package codec

import (
	"errors"
	"geekbang-go-homework/week09/protocol"

	"github.com/Terry-Mao/goim/pkg/bufio"
	"github.com/Terry-Mao/goim/pkg/encoding/binary"
)

const (
	MaxBodySize = int32(1 << 12)
	// size
	_packSize      = 4
	_headerSize    = 2
	_verSize       = 2
	_opSize        = 4
	_seqSize       = 4
	_rawHeaderSize = _packSize + _headerSize + _verSize + _opSize + _seqSize
	_maxPackSize   = MaxBodySize + int32(_rawHeaderSize)
	// offset
	_packOffset   = 0
	_headerOffset = _packOffset + _packSize
	_verOffset    = _headerOffset + _headerSize
	_opOffset     = _verOffset + _verSize
	_seqOffset    = _opOffset + _opSize
)

var (
	// ErrProtoPackLen proto packet len error
	ErrProtoPackLen = errors.New("default server codec pack length error")
	// ErrProtoHeaderLen proto header len error
	ErrProtoHeaderLen = errors.New("default server codec header length error")
)

//Protocol
//Name	Length	Remork
//Package Length	4 bytes	header + body length
//Header Length	2 bytes	protocol header length
//Protocol Version	2 bytes	protocol version
//Operation	4 bytes	operation for request
//Sequence id	4 bytes	sequence number chosen by client
//Body	PackLen + HeaderLen	binary body bytes

// LengthFieldBasedFrameCoder 基于长度字段的编解码器
type LengthFieldBasedFrameCoder struct {
}

func (*LengthFieldBasedFrameCoder) Read(rr *bufio.Reader) (p protocol.Proto, err error) {
	var buf []byte
	if buf, err = rr.Pop(_rawHeaderSize); err != nil {
		return
	}
	packLen := binary.BigEndian.Int32(buf[:_headerOffset])
	headerLen := binary.BigEndian.Int16(buf[_headerOffset:_verOffset])
	p.Ver = int32(binary.BigEndian.Int16(buf[_verOffset:_opOffset]))
	p.Op = binary.BigEndian.Int32(buf[_opOffset:_seqOffset])
	p.Seq = binary.BigEndian.Int32(buf[_seqOffset:])
	if packLen > _maxPackSize {
		return p, ErrProtoPackLen
	}
	if headerLen != _rawHeaderSize {
		return p, ErrProtoHeaderLen
	}
	if bodyLen := int(packLen - int32(headerLen)); bodyLen > 0 {
		p.Body, err = rr.Pop(bodyLen)
	} else {
		p.Body = nil
	}
	return
}
func (*LengthFieldBasedFrameCoder) Write(wr *bufio.Writer, p protocol.Proto) (err error) {
	var (
		buf     []byte
		packLen int32
	)
	packLen = _rawHeaderSize + int32(len(p.Body))
	if buf, err = wr.Peek(_rawHeaderSize); err != nil {
		return
	}
	binary.BigEndian.PutInt32(buf, packLen)
	binary.BigEndian.PutInt16(buf[_headerOffset:], int16(_rawHeaderSize))
	binary.BigEndian.PutInt16(buf[_verOffset:], int16(p.Ver))
	binary.BigEndian.PutInt32(buf[_opOffset:], p.Op)
	binary.BigEndian.PutInt32(buf[_seqOffset:], p.Seq)
	if p.Body != nil {
		_, err = wr.Write(p.Body)
	}
	return
}

func (l *LengthFieldBasedFrameCoder) WriteAndFlush(wr *bufio.Writer, p protocol.Proto) (err error) {
	err = l.Write(wr, p)
	if err == nil {
		return wr.Flush()
	}
	return err
}
